5장. 구조적 설계의 모듈 결합

출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 장연호 옮김, 제이펍 2026) | 공식: https://www.jpub.kr/ | 원서: https://www.manning.com/books/balancing-coupling-in-software-design

결합에도 종류가 있다 — 1960~70년대에 처음으로 6단계로 정리됐다. 콘텐츠·공통·외부·제어·스탬프·데이터 결합. 오래된 모델이지만 "어떤 결합이 얼마나 위험한가"를 보는 눈을 갖춰 줬고, 그 눈은 지금도 유효하다.


학습 목표

이 장을 끝내면 다음을 할 수 있다.

  • 구조적 설계의 모듈 결합 6수준(콘텐츠·공통·외부·제어·스탬프·데이터)을 일상 비유로 설명한다.
  • 두 모듈을 보고 어느 수준의 결합인지 분류한다.
  • 콘텐츠·공통·외부·제어 결합 코드를 데이터 결합 쪽으로 리팩토링한다.
  • 같은 결합이라도 어떤 건 위험하고 어떤 건 받아들일 만한지 판단한다 (10장 결합 균형 미리보기).
  • "고전 모델인데 왜 지금도 배우나"를 답한다 (다음 7장 통합 강도의 토대).

전체 흐름도

결합에도 강도가 있다. 위로 갈수록 결합이 강하고 위험, 아래로 갈수록 약하고 안전.

   강 ▲ ┌──────────────────────────────────────────┐
       │ 1. 콘텐츠 결합 (병리학적)                 │  옆집이 벽 뚫고 들어옴
       │    공개 인터페이스 무시, 내부 직접 침범   │
       ├──────────────────────────────────────────┤
       │ 2. 공통 결합                              │  아파트 공동 게시판
       │    전역 데이터 *전체*를 공유              │
       ├──────────────────────────────────────────┤
       │ 3. 외부 결합                              │  공동 우편함 한 칸
       │    전역 데이터 *일부*만 공유              │
       ├──────────────────────────────────────────┤
       │ 4. 제어 결합                              │  주방에 조리법까지 지시
       │    "어떻게 할지"까지 알려주는 플래그      │
       ├──────────────────────────────────────────┤
       │ 5. 스탬프 결합                            │  명함 통째로 건넴
       │    필요 없는 데이터까지 통째로 넘김       │
       ├──────────────────────────────────────────┤
       │ 6. 데이터 결합 (가장 안전)                │  이름 한 단어만 적은 종이
       │    꼭 필요한 데이터만 주고받음            │
   약 ▼ └──────────────────────────────────────────┘

위쪽일수록: 공유 지식↑ · 변경 전파↑ · 캡슐화 깨짐↑
아래쪽일수록: 공유 지식↓ · 인터페이스 명확↑ · 변경 안전↑

0. 사전 필수 용어

  • 구조적 설계 (Structured Design) — 래리 콘스탄틴이 1963년부터 정리하고 에드워드 요던과 협력해 1975년 출판한 모듈식 설계 방법론. 모듈 결합 6수준 모델이 핵심 산출물.
  • 모듈 결합 (Module Coupling) — 두 모듈이 어떻게 연결돼 있는가의 방식. 강도(coupling level)로 분류한다. 메서드/루틴부터 서비스·전체 시스템까지 모든 추상화 수준에 적용.
  • 상위/하위 (Upstream/Downstream) — 기능을 제공하는 쪽이 상위, 소비하는 쪽이 하위.
  • 공개 인터페이스 / 비공개 구현 세부 — 상위 모듈이 의도적으로 노출한 부분이 공개 인터페이스, 그렇지 않은 모든 것이 비공개 구현 세부.
  • 전역 상태 (Global State) — 여러 모듈이 동시에 읽고 쓸 수 있는 공통 메모리·저장소. 전역 변수, 공유 파일, 공유 DB 모두 해당.
  • DTO (Data Transfer Object) — 통합 전용으로 따로 만든 단순 데이터 객체. 내부 모델과 외부 인터페이스를 분리해 내부 구조 변경이 소비자에게 전파되지 않게 한다.
  • 공유 지식 (Shared Knowledge) — 두 모듈이 서로 알아야 하는 정보의 양. 이 양이 많을수록 결합 강도가 높고 변경 전파 위험이 크다.
  • 복잡한 상호작용 (Intricate Interaction) — 한쪽의 사소한 변경이 시스템 전체에 파급 효과를 일으키는 상태. 콘텐츠·공통 결합에서 주로 발생.

1. 왜 1960년대 모델을 지금 배우나?

『소프트웨어 위기(software crisis)』라는 말이 1969년에 등장했다. "프로그램이 너무 복잡해서 만들기도 고치기도 비싸다"는 문제. 반세기가 지났는데도 똑같은 고민이다. 래리 콘스탄틴과 에드워드 요던이 정리한 6수준 모듈 결합 모델은 표면 코드가 어셈블리·코볼·포트란이지만, 결합을 종류별로 보는 눈은 마이크로서비스·클래스·메서드 어디에나 똑같이 적용된다.

이 장과 6장(공생성)이 소개하는 오래된 모델은 역사 수업이 아니다. 두 모델의 수준과 개념적 차이점을 이해하는 것이 7장 '통합 강도'의 핵심 토대다.


2. 콘텐츠 결합 — 옆집이 벽 뚫고 들어옴 (병리학적)

일상 비유: 옆집 사람이 현관 벨도 누르지 않고 내 집 벽을 뚫고 들어와 내 책상 서랍에서 물건을 꺼내 간다. 내가 책상을 옮기거나 서랍을 바꾸면 옆집 살림이 깨진다. 게다가 그 사람이 들어왔다는 사실조차 모른다.

소프트웨어: 하위 모듈이 상위 모듈의 공개 인터페이스를 무시하고 비공개 구현 세부를 직접 침범한다. 병리학적이라고도 한다.

잘못된 예 — 어셈블리 점프 (원형)

ROUTINE MAIN
  JUMP TO COMP + 18    ; PROCESS 루틴 내부 53번째 줄로 직접 점프
END ROUTINE MAIN

ROUTINE PROCESS
  COMP:
    MOVE 0 TO REGISTER B
END ROUTINE PROCESS

PROCESS 내부 53번째 줄 앞에 줄 하나만 추가해도 MAIN이 잘못된 위치로 점프한다.

잘못된 예 — 현대판: 리플렉션으로 비공개 메서드 호출

public void DoSomething() {
    var invoice = new InvoiceGenerator();
    var t = typeof(InvoiceGenerator);
    var privateMethod = t.GetMethod("VerifyInput",
        BindingFlags.NonPublic | BindingFlags.Instance);
    privateMethod.Invoke(invoice, "input");  // private 메서드를 강제 호출
}

VerifyInput은 비공개 — 외부에서 호출하지 말라는 설계적 의도. 리플렉션으로 우회하는 건 벽을 뚫는 행위. InvoiceGenerator 작성자가 메서드를 삭제하거나 시그니처를 바꾸면 왜 다른 곳이 깨졌는지 추적도 어렵다.

잘못된 예 — 현대판: 다른 서비스의 DB 직접 접근

마이크로서비스 A가 자기 DB를 가지고, B가 그 DB에 직접 접속해 데이터를 읽는다. A의 공식 API를 통하지 않고. A가 컬럼 이름이나 인덱스를 바꾸면 B가 조용히 깨진다 — A는 B가 보고 있다는 사실조차 모르고.

왜 가장 위험한가

  • 캡슐화 경계가 통째로 무너진다 (상위 모듈의 어떤 세부 사항도 변경 불가).
  • 통합이 암묵적이다 — 상위 작성자가 그 결합의 존재를 모를 수 있다.
  • 가장 작은 변경도 시스템 전체를 깨뜨릴 수 있다 (복잡한 상호작용).

3. 공통 결합 — 아파트 공동 게시판

일상 비유: 30가구가 사는 아파트에 공동 게시판이 하나 있어, 모든 가구가 원하는 메모를 자유롭게 쓰고 지운다. 어느 메모를 누가 썼는지·언제 바뀌었는지 추적은 불가능하다.

소프트웨어: 여러 모듈이 전역적으로 공유되는 데이터 구조를 다 같이 읽고 쓴다 — 전역 변수, 공유 파일(S3·GCS·Azure Storage), 공유 DB 등.

잘못된 예 — 포트란 COMMON 블록

SUBROUTINE SUB1 ()
   COMMON /VARS/ ALPHA, BETA, GAMMA, DELTA  ! 4개 변수 통째로 선언·공유
END

SUBROUTINE SUB2 ()
   COMMON /VARS/ ALPHA, BETA, GAMMA, DELTA  ! SUB1의 똑같은 블록 공유
END

SUBROUTINE SUB3 ()
   COMMON /VARS/ ALPHA, BETA, GAMMA, DELTA
END

SUB2BETA 하나만 쓰더라도 블록 전체를 선언해야 한다. BETA의 자료형을 바꾸면 세 서브루틴 모두 동시에 수정해야 한다.

현대판 사례

  • 여러 모듈이 같은 data.json 파일(S3·GCS·Azure Storage)을 읽고 씀
  • 여러 마이크로서비스가 같은 Redis 키를 읽고 씀
  • 같은 클래스 안 여러 메서드가 공유 멤버 변수를 만짐 (이 경우는 자연스러운 공통 결합 — 10장 참조)

왜 위험한가

  • 필요 없는 것까지 다 공유됨 → 통합 계약이 명시적이지 않아 어느 모듈이 어느 데이터를 실제로 쓰는지 분간 불가.
  • 누가 언제 무엇을 바꿨는지 추적 불가 → 부작용이 어디서 왔는지 찾기 어렵다.
  • 데이터 검증 로직(예: "잔액은 0 이상")이 모든 모듈에 복제돼야 함. 한 군데서 빠뜨리면 다른 모듈이 잘못된 상태를 만난다.
  • 동시 쓰기에 직렬화·락이 필요해 성능 저하. 경쟁 조건(race condition) 발생 가능.

공통 결합 vs 콘텐츠 결합 차이: 공통 결합은 의도적으로 공유 메모리를 합의해 사용한다. 콘텐츠 결합은 상위 모듈이 모르거나 동의하지 않은 상태에서 침범당한다.


4. 외부 결합 — 공동 우편함 한 칸

일상 비유: 공동 게시판(공통 결합)이 너무 잡다해서, 두 집 사이에만 우편함 한 칸을 두고 정확히 필요한 쪽지만 주고받기로 한다. 게시판보다 좁고 깔끔하지만, 여전히 공유 공간이라는 본질은 같다.

소프트웨어: 공통 결합과 똑같이 전역 데이터로 통신하지만 공유 데이터의 양이 줄어든 형태. PL/I의 EXTERNAL 속성, C#의 static 변수 등.

잘못된 예 — PL/I EXTERNAL 원형

ProcA: procedure;
  declare A fixed decimal (7,2) external;  /* A는 전역 메모리 공유 */
end ProcA;

ProcB: procedure;
  declare A fixed decimal (7,2) external;  /* 같은 메모리 주소 참조 */
end ProcB;

잘못된 예 — 현대판: static 변수 공유

class ClassA {
    public static string Name { get; set; }  // 전역 정적 변수
}

class ClassB {
    public void SetName(string n) { ClassA.Name = n; }   // 쓰기
}

class ClassC {
    public void Greet() { Console.WriteLine($"Hi {ClassA.Name}!"); }  // 읽기
}

공통 결합보다 공유 폭은 좁아졌지만 (1개 변수만 공유), 부작용 추적·동시 수정 문제·검증 로직 중복은 그대로다.

공통 결합과 비교

측면 공통 결합 외부 결합
공유 데이터 양 전부 필요한 것만
통신 방식 전역 메모리 전역 메모리 (동일)
동시 수정 위험
부작용 추적 어려움 어려움
결합 수준 더 강함 조금 약함

5. 제어 결합 — 식당 주방에 조리법까지 지시

일상 비유: 식당에서 "스테이크 미디엄으로 주세요"는 무엇만 시킨 것. 그런데 "팬은 주물팬 써주시고, 센 불 2분 + 약불 5분, 양파 가니쉬는 빼고, 접시는 미리 데워서" 이런 식이면 주방의 내부 작동까지 지시하는 거다. 다음에 식당이 조리법을 바꾸려면 나한테 일일이 전화해서 확인해야 한다.

소프트웨어: 한 모듈이 플래그·명령·옵션 인수를 넘겨 다른 모듈의 내부 실행 분기를 직접 제어한다. 수신 모듈이 기능을 완전히 캡슐화하지 못한다는 신호.

잘못된 예

function sendNotification(type, message) {
  switch (type) {
    case 'sms':   sendSMS(message); break;
    case 'email': sendEmail(message); break;
    case 'push':  sendPushNotification(message); break;
    default: throw new Error("Notification type not supported");
  }
}

// 호출자(notifyUser)는 'sms', 'email', 'push' 값을 *알아야* 함
function notifyUser(user, message) {
  let notificationType;
  if (user.preferences.receiveSMS && user.phoneNumber) {
    notificationType = 'sms';
  } else if (user.preferences.receiveEmail && user.email) {
    notificationType = 'email';
  } else {
    notificationType = 'push';
  }
  sendNotification(notificationType, message);
}

sendNotificationpush를 더 이상 지원하지 않게 바꾸면, push를 넘기던 모든 호출자가 런타임 에러. 호출자가 내부 분기 종류를 알아야 한다는 것 자체가 결합 강도다.

올바른 예 — 의도만 노출, 분기는 내부화

// 호출자는 "사용자에게 알려"만 알면 됨
function notifyUser(user, message) {
  // 내부에서 user.preferences에 따라 채널 선택 — 외부에 안 보임
  const channel = pickChannel(user);
  channel.send(message);
}

notifyUser(user, "Hi");  // 호출자는 분기 키워드 모름

제어 결합의 신호

  • 함수 인수에 type·mode·flag·action 같은 분기 키워드가 자주 보임
  • 호출자가 내부 case를 인지하고 호출해야 함
  • 새 case 추가/삭제 시 호출 코드까지 같이 바꿔야 함

제어 결합 vs 외부 결합: 제어 결합은 전역 상태 대신 명시적 인수를 전달하므로 외부 결합보다 낮은 수준이다. 하지만 상위 모듈이 기능을 완전히 캡슐화하지 못한다는 점에서 여전히 강한 결합이다.


6. 스탬프 결합 — 명함 통째로 건넴

일상 비유: 친구에게 "이름만 알려 줘"라 했는데 명함 한 장 통째로 건네 준다. 이름·전화번호·이메일·주소·회사·직책이 다 적혀 있다. 친구는 이름만 보지만, 명함 양식이 바뀌면 명함 만드는 회사는 모든 명함 받는 사람을 신경 써야 한다.

소프트웨어: 모듈이 필요한 필드 한두 개만 쓰려는데, 상위 모듈이 수십 개 필드를 가진 객체 전체를 넘겨 준다.

잘못된 예

namespace Example.CRM {
  public class CustomersRepository {
    public Customer Get(Guid id) { ... }  // Customer = 수백 개 필드
  }
}

namespace Example.Analysis {
  public class Assessment {
    void Execute(Guid customerId) {
      var repository = new CustomersRepository();
      var status = repository.Get(customerId).Status;  // Status 하나만 쓰는데 전체 받음
    }
  }
}

Customer에 새 필드를 추가하거나 기존 필드 타입을 바꾸면 모든 소비자가 잠재적으로 영향받는다. CRM 작성자는 "혹시 누가 이 필드 쓰는 거 아닐까" 항상 걱정해야 한다.

스탬프 결합 vs 공통 결합: 스탬프 결합은 데이터가 전역으로 수정 가능한 방식이 아니라 메서드 호출을 통해 공유된다. 비즈니스 로직 공유 X, 동시성 문제 X. 단지 경계가 헐겁다는 것이 문제.

스탬프 결합 vs 제어 결합: 제어 결합은 기능·로직에 대한 지식을 공유하고, 스탬프 결합은 데이터 구조에 대한 지식을 공유한다. 데이터 구조 지식이 행동 지식보다 더 안정적이므로 스탬프 결합이 더 낮은 수준이다.


7. 데이터 결합 — 이름 한 단어만 적은 종이 (가장 안전)

일상 비유: 친구에게 "이름만 알려 줘"라 하니 이름 한 단어만 적은 메모를 준다. 필요한 것만, 깔끔. 친구가 명함 양식을 바꿔도 나에겐 영향 없음 — 우리 사이엔 "이름 한 단어"라는 합의된 작은 약속만 있다.

소프트웨어: 모듈이 통합에 꼭 필요한 최소 데이터만 주고받는다. 비즈니스 로직 공유 X, 불필요 필드 공유 X. 항상 명시적으로 정의된 공개 인터페이스를 통해 통합.

올바른 예 — 필요한 값만 반환하는 메서드 분리

namespace Example.CRM {
  public class CustomersRepository {
    public Status GetStatus(Guid customerId) { ... }  // Status 하나만 반환
  }
}

namespace Example.Analysis {
  public class Assessment {
    void Execute(Guid customerId) {
      var repository = new CustomersRepository();
      var status = repository.GetStatus(customerId);  // 받는 것도 Status 하나
    }
  }
}

Customer 내부 구조가 어떻게 바뀌든 GetStatus의 반환 타입(Status)만 안정적이면 Analysis는 영향 없다.

극단판 — DTO로 내부·외부 완전 분리

// 통합 전용 DTO 정의
namespace Example.CRM.Integration.DTOs {
  public class CustomerSnapshot {
    public static CustomerSnapshot From(Customer c) { ... }  // 내부 → 외부 변환
  }
}

namespace Example.CRM {
  public class CustomersRepository {
    public CustomerSnapshot Get(Guid customerId) { ... }  // 내부 Customer가 아닌 Snapshot 반환
  }
}

namespace Example.Analysis {
  public class Assessment {
    void Execute(Guid customerId) {
      var repository = new CustomersRepository();
      var customer = repository.Get(customerId);  // CustomerSnapshot만 받음
    }
  }
}

내부 Customer는 자유롭게 진화시켜도 외부 인터페이스CustomerSnapshot만 안정적이면 된다. 내부 설계와 통합 특화 객체(DTO)는 서로 다른 속도로 진화할 수 있다. 이것이 결합 균형의 첫 형태 — 안 변하는 약속(외부)과 자주 변하는 구현(내부)을 분리.


8. 결합 수준 한 줄 묶음 비교표

수준 일상 비유 핵심 신호 공유 지식
콘텐츠 (강) 옆집이 벽 뚫고 들어옴 리플렉션·private 침범·남의 DB 직접 접속 구현 세부 전부
공통 아파트 공동 게시판 전역 변수·공유 파일·공유 DB 전역 데이터 전부
외부 공동 우편함 한 칸 static 변수·EXTERNAL 키워드 전역 데이터 일부
제어 식당에 조리법까지 지시 type·mode·flag 인수 내부 로직·설계 결정
스탬프 명함 통째로 건넴 50개 필드 객체에서 1개만 씀 데이터 구조
데이터 (약) 이름 한 단어만 적은 메모 필요한 값만, DTO로 내·외부 분리 최소 데이터만

참고 — "공통·외부 결합 = 무조건 나쁘다"는 아니다. 같은 클래스 안 메서드들이 공유 멤버 변수를 만지는 건 자연스러운 공통 결합이다. 10장 '결합 균형'에서 언제 받아들일 만한지 자세히 다룬다.


핵심 개념 정리

개념 한 줄
모듈 결합 6수준 콘텐츠→공통→외부→제어→스탬프→데이터 (강→약)
콘텐츠 결합 공개 인터페이스 무시·내부 침범. 가장 위험 (병리학적)
공통 결합 전역 데이터 전체 공유. 추적 불가·비즈니스 로직 중복
외부 결합 전역 데이터 일부만 공유. 공통보다 좁지만 단점 동일
제어 결합 type/flag내부 분기까지 지시. 캡슐화 실패 신호
스탬프 결합 필요 없는 필드까지 통째로 넘김. 비즈니스 로직 공유는 없음
데이터 결합 꼭 필요한 데이터만. 가장 안전
DTO 통합 전용 외부 노출 객체로 내·외부 분리
공유 지식 ↑ = 결합 강도 ↑ 지식이 많이 새 나갈수록 변경 전파↑·복잡한 상호작용↑
구조적 설계의 목표 비용 효율적·안정적·유연한 소프트웨어 = 낮은 결합 + 높은 모듈성

실무 체크리스트

  • [ ] 다른 모듈/서비스의 비공개 메서드를 리플렉션·우회로 호출하는 코드가 있는가? (콘텐츠 결합)
  • [ ] 다른 마이크로서비스의 DB에 공식 API 없이 직접 접속하는 곳이 있는가? (콘텐츠 결합)
  • [ ] 여러 모듈이 같은 전역 파일/Redis 키/S3 객체를 동시에 읽고 쓰는가? (공통 결합)
  • [ ] 여러 클래스가 같은 static 변수를 공유하는가? (외부 결합)
  • [ ] 함수 인수에 type·mode·action·flag 같은 분기 키워드가 있고 호출자가 그 값을 알아야 하는가? (제어 결합)
  • [ ] 함수가 수십 개 필드 객체를 받으면서 정작 1~2개 필드만 쓰는가? (스탬프 결합)
  • [ ] 외부 노출용 DTO와 내부 모델이 같은 객체를 쓰고 있는가? 분리하면 진화가 자유로워진다 (데이터 결합으로 격상)
  • [ ] 새 코드 작성 시 기본값은 데이터 결합. 더 강한 결합이 필요하면 이유를 댈 수 있어야 한다

연습문제 (문제만 — 정답은 부록 D)

  1. 분류. 다음 코드의 결합 수준은 무엇이고, 그 이유는? python class OrderService: def place(self, order): # 다른 모듈 PaymentService의 private 메서드를 직접 호출 PaymentService._validate_card_internally(order.card)

  2. 현대판 식별. 한 회사의 OrderServiceReportService같은 PostgreSQL 데이터베이스의 같은 orders 테이블에 둘 다 직접 SELECT·INSERT를 한다. 둘 사이의 결합은 어느 수준인가? 어떤 위험이 있나?

  3. 리팩토링. 다음 함수의 결합 수준을 식별하고, 한 단계 낮은 결합으로 리팩토링하라. javascript function processItem(item, action) { switch (action) { case 'discount': item.price *= 0.9; break; case 'tax': item.price *= 1.1; break; case 'archive': item.status = 'archived'; break; } }

  4. DTO 적용. Customer 객체가 30개 필드를 가지고 있고 Analysis 모듈은 emailsignupDate 두 필드만 쓴다. 데이터 결합으로 격상하는 두 가지 방법(메서드 분리 / DTO 도입)을 각각 코드로 보여라.

  5. 판단. 5.4절 '요점'의 세 가지 진단 질문(공유 DB, 데이터 구조 교환, 구현 세부 의존)을 실제 마이크로서비스 아키텍처에 적용할 때 어떤 순서로 점검하는 게 효율적인가? 이유와 함께 설명하라.


최신 동향 (2026-05 기준)

최신 동향 (검증 2026-05-31) — 6수준 모델 원리는 그대로 유효. 분산 시스템 시대엔 콘텐츠 결합이 "다른 서비스 DB 직접 접근"의 형태로 가장 자주 나타나, 공유 DB 안티패턴으로 별도 카탈로그화돼 있다.

  • 공유 DB 안티패턴 — 마이크로서비스 패턴 카탈로그에 Database per Service 패턴으로 명시. "한 DB를 여러 서비스가 공유"는 콘텐츠/공통 결합의 분산판이라 회피 권장.
  • 계약 우선 통합 — 데이터 결합의 분산판으로 OpenAPI / AsyncAPI 같은 명시적 계약 표준이 표준 도구로 자리잡음. DTO 개념의 API 레벨 형태.
  • 공생성(6장)·통합 강도(7장) — 5장 6수준 모델은 독립 이론이 아니라 7장 '통합 강도' 실용 모델의 개념적 토대. 이 장을 공부한 후 6·7장으로 이어가야 완전한 그림이 보인다.

부록 A. 용어 사전

한글 용어 원문 영문명 의미
모듈 결합 Module Coupling 두 모듈의 연결 방식. 강도로 분류
콘텐츠 결합 Content Coupling 비공개 구현을 직접 침범. 가장 강함 (병리학적)
공통 결합 Common Coupling 전역 데이터 전체를 공유
외부 결합 External Coupling 전역 데이터 일부만 공유
제어 결합 Control Coupling type/flag로 내부 분기를 외부에서 지시
스탬프 결합 Stamp Coupling 필요 이상의 큰 데이터 구조를 통째 전달
데이터 결합 Data Coupling 꼭 필요한 데이터만 전달. 가장 약함
전역 상태 Global State 여러 모듈이 동시에 읽고 쓰는 공통 저장 공간
DTO Data Transfer Object 통합 전용으로 따로 만든 단순 데이터 객체
캡슐화 경계 Encapsulation Boundary 모듈이 외부에 드러내는 면과 숨기는 면의 구분선
공유 지식 Shared Knowledge 두 모듈이 서로 알아야 하는 정보의 양. 많을수록 결합 강함
복잡한 상호작용 Intricate Interaction 한쪽 변경이 시스템 전체에 파급되는 상태
구조적 설계 Structured Design 콘스탄틴·요던(1975)의 모듈식 설계 방법론
병리학적 결합 Pathological Coupling 콘텐츠 결합의 다른 이름
소프트웨어 위기 Software Crisis 1969년에 등장한 용어. 복잡·비용·일정 문제

부록 B. 핵심 비교표

6수준 한눈에 — 공유 지식·통신 방식·위험

수준 공유 지식 통신 방식 변경 전파 위험 비즈니스 로직 공유
콘텐츠 구현 세부 전부 비공식·암묵 침범 가장 큼 O (가능)
공통 전역 데이터 전부 전역 메모리/저장소 O
외부 전역 데이터 일부 전역 메모리/저장소 O
제어 내부 로직·설계 결정 명시적 인수(flag) 중간 O
스탬프 데이터 구조 명시적 인수(객체) 중간-낮음 X
데이터 최소 데이터 명시적 인수(원시값/DTO) 가장 낮음 X

공통·외부·스탬프 결합 — 헷갈리는 셋 정리

구분 공통 외부 스탬프
통신 방식 전역 메모리 전역 메모리 메서드 호출
비즈니스 로직 공유 O O X
동시성 문제 O O X
데이터 양 전부 필요한 것만 필요 이상의 객체

부록 C. 추천 참고 자료 & 링크

Tier 1 공식·표준

자료 링크
책 공식 (제이펍) jpub.kr
원서 — Manning Balancing Coupling in Software Design
마이크로서비스 패턴 — Chris Richardson microservices.io/patterns
Database per Service 패턴 microservices.io
OpenAPI 표준 (계약 우선 통합) openapis.org
AsyncAPI 표준 (이벤트 기반 계약) asyncapi.com
Constantine·Yourdon 구조적 설계 (전자판) archive.org

책 다른 장 안내

설명
4장 모듈성 — 결합의 반대편 (5장 선수)
6장 공생성 — 또 다른 결합 모델 (5장과 함께 7장의 토대)
7장 통합 강도 — 5·6장 두 모델을 통합해 실용화
10장 결합 균형 — 언제 공통/외부 결합이 받아들일 만한가

부록 D. 연습문제 풀이

  1. (콘텐츠 결합) _validate_card_internally라는 비공개(_ 접두) 메서드를 외부 모듈에서 직접 호출하고 있다. 공개 인터페이스를 우회해 구현 세부에 침범하는 행위 = 콘텐츠 결합(병리학적). 위험: PaymentService 작성자가 메서드 이름·시그니처를 바꾸거나 삭제하면 OrderService조용히 깨지고, PaymentService 쪽에서 자신이 이 호출의 존재를 모를 가능성이 높다.

  2. (콘텐츠 결합의 분산판 = 공유 DB 안티패턴) 두 서비스가 한 DB의 같은 테이블에 직접 SELECT·INSERT한다는 건 공식 API를 우회해 내부 데이터 저장소에 침범하는 형태. 본질은 콘텐츠 결합. 위험: ① 한 서비스가 컬럼·인덱스·제약을 바꾸면 다른 서비스가 깨짐 ② 트랜잭션 경계가 흐려져 데이터 일관성 깨짐 ③ 두 서비스가 서로의 변경을 인지하지 못함. 해결: 한쪽이 데이터를 소유하고 다른 쪽은 공식 API(REST/이벤트)로만 접근 (Database per Service 패턴).

  3. (제어 결합 → 데이터 결합 + 메서드 분리) action 문자열이 내부 분기를 결정하는 제어 결합. 리팩토링: ```javascript // 의도별 메서드로 분리 — 호출자는 분기 키워드 모름 function applyDiscount(item) { item.price = 0.9; } function applyTax(item) { item.price = 1.1; } function archive(item) { item.status = 'archived'; }

// 호출자 applyDiscount(myItem); // 의도만 전달, 내부 로직 모름 ``` 호출자는 의도만 알면 되고, 새 action 추가/삭제 시 다른 호출자에게 영향 없음.

  1. (메서드 분리 방식 / DTO 방식) ```python # 방식 A: 필요한 값만 반환하는 메서드 분리 class CustomerRepo: def get_signup_info(self, id): c = self._fetch(id) return c.email, c.signup_date # 두 값만 반환

# 방식 B: 통합 전용 DTO 도입 from dataclasses import dataclass from datetime import date

@dataclass class CustomerSignupInfo: email: str signup_date: date

class CustomerRepo: def get_signup_info(self, id) -> CustomerSignupInfo: c = self._fetch(id) return CustomerSignupInfo(c.email, c.signup_date) `` 둘 다Analysis` 모듈이 Customer 30개 필드를 모르게 된다. 방식 B(DTO)는 데이터 구조 자체에 이름을 붙여 의미가 분명해지고, 나중에 필드를 추가해도 호환성 관리가 쉬워진다.

  1. (점검 순서: 공유 DB → 구현 세부 의존 → 데이터 구조 교환) 영향이 큰 순서로 점검하는 것이 효율적이다. ① 공유 DB(콘텐츠/공통 결합): 서비스 전체를 실질적으로 하나로 묶으므로 가장 먼저 제거해야 한다. 발견 시 Database per Service 패턴 적용. ② 구현 세부 의존(콘텐츠 결합): 다른 서비스의 비공개 API·내부 파일·리플렉션 사용 여부 점검. ③ 데이터 구조 교환(스탬프 vs 데이터 결합): API 응답이 내부 엔티티를 그대로 노출하는지, DTO/뷰 모델로 분리했는지 확인. 영향 범위가 서비스 계층 → 인터페이스 레벨 → 개별 필드 수준 순으로 좁아지므로 이 순서가 가장 효율적이다.
난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플